AWS CDK で Amazon SES の Identity にメールアドレスを追加する
こんにちは、製造ビジネステクノロジー部の若槻です。
前回の記事では、Amazon SES でカスタムドメインを使用してメールを受信する構成を AWS CDK で実装してみました。
しかし上記記事の設定だけではメールアドレスの Identity としての承認を行っていないため、例えば下記記事のような設定を行う際に Amazon Cognito で Amazon SES をメールプロバイダーとすることができません。
そこで今回は、AWS CDK で Amazon SES の Identity にメールアドレスを追加し、手動で Identity の承認を行うところまでを試してみます。
やってみた
CDK 実装
AWS CDK のスタック実装のコードは以下の通りです。
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as route53 from 'aws-cdk-lib/aws-route53';
import * as ses from 'aws-cdk-lib/aws-ses';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as ses_actions from 'aws-cdk-lib/aws-ses-actions';
import * as cr from 'aws-cdk-lib/custom-resources';
export class MainStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);
const region = this.region;
const domainName = process.env.DOMAIN_NAME || '';
const noreplyEmailAddress = `noreply@${domainName}`;
// 既存のホストゾーンを取得
const hostedZone = route53.PublicHostedZone.fromLookup(this, 'HostedZone', {
domainName,
});
// SES Domain Identity
// MEMO: DKIM レコードの追加は既定で自動的に行われる
new ses.EmailIdentity(this, 'SesEmailIdentity', {
identity: ses.Identity.publicHostedZone(hostedZone),
});
// MXレコードの追加
new route53.MxRecord(this, 'SESMXRecord', {
zone: hostedZone,
values: [
{
// @see https://docs.aws.amazon.com/general/latest/gr/ses.html#ses_inbound_endpoints
hostName: `inbound-smtp.${region}.amazonaws.com`,
priority: 10,
},
],
ttl: cdk.Duration.minutes(5),
});
// その他のレコードの追加は省略
// 受信メール格納用バケット
const incomingEmailBucket = new s3.Bucket(this, 'IncomingEmailBucket', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
// SES ルールセット
// MEMO: ルールセットのアクティブ化は自動で行われない
const ruleSet = new ses.ReceiptRuleSet(this, 'SESRuleSet', {
rules: [
{
recipients: [noreplyEmailAddress],
actions: [
new ses_actions.S3({
bucket: incomingEmailBucket,
objectKeyPrefix: 'incoming/',
}),
],
},
],
});
// ルールセットをアクティブ化/非アクティブ化するカスタムリソース
new cr.AwsCustomResource(this, 'SetActiveRuleSet', {
onCreate: {
service: 'SES',
action: 'setActiveReceiptRuleSet',
parameters: {
RuleSetName: ruleSet.receiptRuleSetName,
},
physicalResourceId: cr.PhysicalResourceId.of('SetActiveRuleSet'),
},
onUpdate: {
service: 'SES',
action: 'setActiveReceiptRuleSet',
parameters: {
RuleSetName: ruleSet.receiptRuleSetName,
},
physicalResourceId: cr.PhysicalResourceId.of('SetActiveRuleSet'),
},
onDelete: {
service: 'SES',
action: 'setActiveReceiptRuleSet',
parameters: {}, // 空のパラメータですべてのアクティブなルールセットを解除
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
// noreplyEmailAddress の Email Identity への追加
new ses.EmailIdentity(this, 'SesEmailIdentityNoreply', {
identity: ses.Identity.email(noreplyEmailAddress),
});
}
}
末尾の SesEmailIdentityNoreply
の実装以外は前回の記事と同じ実装となります。EmailIdentity コンストラクトクラスを使用すると、ドメインだけでなくメールアドレスも Identity として追加することができます。
ちなみに Identity に追加したドメインやメールアドレスでメールの受信を行わない場合でも、初回の承認メールの受信のために S3 バケットと SES ルールセットは必要となります。
上記の CDK スタックをデプロイします。
デプロイ後の確認
SES コンソールを開いて、Identities にメールアドレスが未承認のステータスで追加されていることを確認します。
そして S3 コンソールを開き、受信メール格納用バケットのプレフィクス配下に、新しくオブジェクトが追加されていることを確認します。
オブジェクトを開くと、以下のようなメールが届いていることを確認します。
Dear Amazon Web Services Customer,
We have received a request to authorize this email address for use with Amazon SES and Amazon Pinpoint in region Asia Pacific (Tokyo). If you requested this verification, please go to the following URL to confirm that you are authorized to use this email address:
https://email-verification.ap-northeast-1.amazonaws.com/?Context=.....
Your request will not be processed unless you confirm the address using this URL. This link expires 24 hours after your original verification request.
If you did NOT request to verify this email address, do not click on the link. Please note that many times, the situation isn't a phishing attempt, but either a misunderstanding of how to use our service, or someone setting up email-sending capabilities on your behalf as part of a legitimate service, but without having fully communicated the procedure first.
To learn more about sending email from Amazon Web Services, please refer to the Amazon SES Developer Guide at http://docs.aws.amazon.com/ses/latest/DeveloperGuide/Welcome.html and Amazon Pinpoint Developer Guide at http://docs.aws.amazon.com/pinpoint/latest/userguide/welcome.html.
Sincerely,
The Amazon Web Services Team.
メールアドレスの Identity を承認する
先述のメールに記載されている URL にアクセスします。次のような画面となればメールアドレスの Identity の承認が完了です。
Identity 一覧でも承認が完了したことが確認できました。
メールアドレスが SES で承認されていない場合
Cognito user pool に CDK でメールによる多要素認証を設定しようとした際に、使用するメールアドレスが SES で未承認の場合、デプロイ時に下記のようなエラーが発生します。
$ npm run deploy
> [email protected] deploy
> cdk deploy --require-approval never --method=direct
✨ Synthesis time: 5.41s
Main: deploying... [1/1]
Main: updating stack...
5:17:52 PM | UPDATE_FAILED | AWS::Cognito::UserPool | MyUserPoolD09D1D74
Resource handler returned message: "Cognito received the following error from Amazon SES when attempting to send email: Email address is not verified. The following identities failed the check in region AP-NORTHEAST-1: ar
n:aws:ses:ap-northeast-1:XXXXXXXXXXXX:identity/[email protected] (Service: CognitoIdentityProvider, Status Code: 400, Request ID: 7061cfbb-a6ff-4bb6-9382-b7fe47b22241)" (RequestToken: c57a9893-597f-de1
e-b300-54c21483eb48, HandlerErrorCode: InvalidRequest)
❌ Main failed: The stack named Main failed to deploy: UPDATE_ROLLBACK_COMPLETE: Resource handler returned message: "Cognito received the following error from Amazon SES when attempting to send email: Email address is not verified. The following identities failed the check in region AP-NORTHEAST-1: arn:aws:ses:ap-northeast-1:XXXXXXXXXXXX:identity/[email protected] (Service: CognitoIdentityProvider, Status Code: 400, Request ID: 7061cfbb-a6ff-4bb6-9382-b7fe47b22241)" (RequestToken: c57a9893-597f-de1e-b300-54c21483eb48, HandlerErrorCode: InvalidRequest)
Idenfity の追加と承認を忘れずに行うようにしましょう。
おわりに
AWS CDK で Amazon SES の Identity にメールアドレスを追加し、手動で Identity の承認を行うところまでを試してみました。
メールアドレスの承認のためにデプロイ時に初回だけ手動での承認作業が必要となるのでリリース作業時などは注意が必要です。承認を自動化したい場合はカスタムリソースなどを使って工夫するしかなさそうですね。
以上